﻿using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using Communications.PropertySearch.Resources;
using WP7Contrib.Collections;
using WP7Contrib.Communications;

namespace Communications.PropertySearch
{
    using System.Reactive.Linq;
    using System.Reactive.Subjects;

    public sealed class PropertySearchServices : ISearchForProperties
    {
        private const string FailedNestoria = "PropertySearchServices: Failed to call Nestoria web service, message - '{0}.";

        private readonly IConfiguration _configuration;
        private readonly IHandleResources _resourceClient;
        private readonly object _sync = new object();

        private DateTime _lastSuccessful;

        public PropertySearchServices(IHandleResources resourceClient, IConfiguration configuration)
        {
            _resourceClient = resourceClient;
            _configuration = configuration;

            var resourceType = _configuration.NestoriaEncoding.ToLowerInvariant() == "json" ? ResourceType.Json : ResourceType.Xml;
            _resourceClient.ForType(resourceType)
                .UseUrlForGet(_configuration.NestoriaUrl);
            
            _lastSuccessful = DateTime.Now.AddSeconds(-2 * _configuration.NestoriaTimeout);
        }

        public IObservable<PagedList<Property>> Search(int pageIndex, int pageSize, PropertyCriteria criteria)
        {
            try
            {
                // Build query string parameters - start with search infrastructure values...
                var sb = new StringBuilder();
                sb.Append("action=" + _configuration.NestoriaAction);
                sb.Append("&version=" + _configuration.NestoriaVersion);
                sb.Append("&encoding=" + _configuration.NestoriaEncoding);
                sb.Append("&page=" + pageIndex);
                sb.Append("&number_of_results=" + pageSize);

                // Add the search critieria values to the string builder...
                sb.Append(criteria.SerializeToString());
                var @params = new object[] { sb.ToString() };

                lock (_sync)
                {
                    Debug.WriteLine("***************************************************************");
                    Debug.WriteLine("PropertySearchServices: Last executed search - " + _lastSuccessful.ToString("HH:MM:ss.ffff"));

                    ThrottleRequests();

                    var observable = new Subject<PagedList<Property>>();

                    _resourceClient.Get<Result>(@params)
                        .Select(ProcessResponse)
                        .Subscribe(cs =>
                        {
                            observable.OnNext(cs);
                            observable.OnCompleted();
                        }, exception =>
                        {
                            observable.OnError(exception);
                            observable.OnCompleted();
                        });

                    _lastSuccessful = DateTime.Now;
                    Debug.WriteLine("PropertySearchServices: Current executed search - " + _lastSuccessful.ToString("HH:MM:ss.ffff"));
                    Debug.WriteLine("***************************************************************");

                    return observable;
                }
            }
            catch (Exception exn)
            {
                var message = string.Format(FailedNestoria, exn.Message);
                Debug.WriteLine(message);
                throw new ServiceException(message, exn);
            }
        }
      
        private void ThrottleRequests()
        {
            var now = DateTime.Now;
            var timeDifference = (int) (now - _lastSuccessful).TotalMilliseconds;

            Debug.WriteLine(string.Format("PropertySearchServices: Time since last search - {0} ms", timeDifference));
            if (timeDifference < _configuration.NestoriaTimeout)
            {
                var remainder = (_configuration.NestoriaTimeout - timeDifference) + 1;
                Debug.WriteLine(string.Format("PropertySearchServices: Throttling for Nestoria (wait = {0} ms), paused for {1} ms",
                                _configuration.NestoriaTimeout,
                                remainder));

                Thread.CurrentThread.Join(remainder);
            }
        }

        private static PagedList<Property> ProcessResponse(Result result)
        {
            var properties = new PagedList<Property>();

            foreach (var listing in result.Response.Listings)
                properties.Items.Add(Map(listing));

            var pageSizeDouble = Convert.ToDouble(result.Response.ResultsCount) / result.Response.PageCount;
            
            properties.Total = Convert.ToInt32(result.Response.ResultsCount);
            properties.PageIndex = result.Response.Page;
            properties.PageSize = (int)Math.Round(pageSizeDouble);
            return properties;
        }

        private static Property Map(Listing listing)
        {
            var property = new Property
                               {
                                   AuctionDate = listing.AuctionDate,
                                   BathroomCount = listing.BathroomCount,
                                   BedroomCount = listing.BedroomCount,
                                   CarSpaces = listing.CarSpaces,
                                   Commission = listing.Commission,
                                   ConstructionYear = listing.ConstructionYear,
                                   DatasourceName = listing.DatasourceName,
                                   Guid = listing.Guid,
                                   GuidePriceMax = Convert.ToDecimal(listing.GuidePriceMax),
                                   GuidePriceMin = Convert.ToDecimal(listing.GuidePriceMin),
                                   ImageHeight = listing.ImageHeight,
                                   ImageUrl = listing.ImageUrl,
                                   ImageWidth = listing.ImageWidth,
                                   Keywords = listing.Keywords,
                                   LandSize = Convert.ToDecimal(listing.LandSize),
                                   LandSizeUOM = listing.LandSizeUom,
                                   Latitude = Convert.ToDecimal(listing.Latitude),
                                   ListerName = listing.ListerName,
                                   ListerUrl = listing.ListerUrl,
                                   ListingType = ListingTypeFromString(listing.ListingType),
                                   Longitude = Convert.ToDecimal(listing.Longitude),
                                   Price = Convert.ToDecimal(listing.Price),
                                   PriceCurrency = listing.PriceCurrency,
                                   PriceFormatted = listing.PriceFormatted,
                                   PriceType = PriceTypeFromString(listing.PriceType),
                                   PropertyType = PropertyTypeFromString(listing.PropertyType),
                                   RoomCount = listing.RoomCount,
                                   Summary = listing.Summary,
                                   ThumbHeight = listing.ThumbHeight,
                                   ThumbUrl = listing.ThumbUrl,
                                   ThumbWidth = listing.ThumbWidth,
                                   Title = listing.Title,
                                   UpdatedInDays = listing.UpdatedInDays,
                                   UpdatedInDaysFormatted = listing.UpdatedInDaysFormatted
                               };

            return property;
        }

        private static PropertyType PropertyTypeFromString(string value)
        {
            return string.IsNullOrEmpty(value)
                       ? PropertyType.Undefined
                       : (PropertyType) Enum.Parse(typeof (PropertyType), value, true);
        }

        private static ListingType ListingTypeFromString(string value)
        {
            return string.IsNullOrEmpty(value)
                       ? ListingType.Undefined
                       : (ListingType)Enum.Parse(typeof(ListingType), value, true);
        }

        private static PriceType PriceTypeFromString(string value)
        {
            return string.IsNullOrEmpty(value)
                       ? PriceType.Undefined
                       : (PriceType)Enum.Parse(typeof(PriceType), value, true);
        }
    }
}